package com.baofu.paycore.biz.paycore.handler.impl;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.baofu.clear.service.facade.enums.ClearStatusEnum;
import com.baofu.clear.service.facade.model.resp.NotifyDTO;
import com.baofu.paycore.biz.engine.command.Command;
import com.baofu.paycore.biz.engine.executor.CommandExecutor;
import com.baofu.paycore.biz.engine.generator.impl.RefundGenerator;
import com.baofu.paycore.biz.engine.result.CommandResult;
import com.baofu.paycore.biz.external.clear.ClearQueryService;
import com.baofu.paycore.biz.external.clear.model.ClearFundResBO;
import com.baofu.paycore.biz.model.PayResBO;
import com.baofu.paycore.biz.paycore.converter.BO2BOConverter;
import com.baofu.paycore.biz.paycore.converter.CommandConverter;
import com.baofu.paycore.biz.paycore.handler.BaseHandler;
import com.baofu.paycore.common.enums.FundTypeEnum;
import com.baofu.paycore.common.enums.OperatorEnum;
import com.baofu.paycore.manager.model.*;
import com.baofu.paycore.manager.storage.impl.PayManager;
import com.baofu.paycore.manager.storage.impl.RefundManager;
import com.baofu.paycore.manager.storage.impl.RefundStrategyManager;
import com.baofu.paycore.manager.storage.impl.SubPayManager;
import com.baofu.paycore.manager.storer.PayOrderRepository;
import com.baofu.paycore.manager.storer.RefundOrderRepository;
import com.baofu.paycore.service.facade.enums.PayCoreErrorCode;
import com.baofu.paycore.service.facade.enums.PayStatusEnums;
import com.baofu.paycore.service.facade.enums.PayToolRouteEnum;
import com.baofu.paycore.service.facade.enums.PayToolSubTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 退款处理服务
 * <p>
 *     1、退款处理流程
 * </p>
 * User: kwl Date: 2016/6/12 ProjectName: paycore Version: 5.0.0
 */
@Slf4j
@Service
public class RefundHandler extends BaseHandler<RefundBO,NotifyDTO> {

    /**
     * 退款支付指令生成
     */
    @Autowired
    private RefundGenerator refundGenerator;

    /**
     * 退款支付指令执行
     */
    @Autowired
    private CommandExecutor commandExecutor;

    /**
     * 退款订单处理
     */
    @Autowired
    private RefundOrderRepository refundOrderRepository;

    /**
     * 退款资金单处理Manager
     */
    @Autowired
    private RefundManager refundManager;


    /**
     * 退款策略配置管理manager
     */
    @Autowired
    private RefundStrategyManager refundStrategyManager;

    /**
     * 清分查询服务
     */
    @Autowired
    private ClearQueryService clearQueryService;

    /**
     * 支付单管理manager
     */
    @Autowired
    private PayManager payManager;

    /**
     * spring事物模版
     */
    @Autowired
    private TransactionTemplate transactionTemplate;

    /**
     * 支付订单服务
     */
    @Autowired
    private PayOrderRepository payOrderRepositoryImpl;

    /**
     * 支付子单服务
     */
    @Autowired
    private SubPayManager subPayManager;



    /**
     * 退款处理流程
     *
     * @param refundBO    退款处理请求对象
     * @return            退款处理返回信息
     */
    public PayResBO refund(RefundBO refundBO){

        //流程处理
        doExecute(refundBO);

        return BO2BOConverter.payBO2PayResBO(refundBO);
    }

    @Override
    public RefundBO activeOrder(String orderId) { return refundOrderRepository.load(orderId); }

    @Override
    public RefundBO queryOrderByPayNo(String orderId) {
        return refundManager.queryByPayNo(orderId,null);
    }

    @Override
    protected void checkBeforeProcess(RefundBO refundBO) { }

    /**
     * 退款处理流程
     *
     * @param refundBO    退款处理请求参数
     */
    @Override
    protected void processOrders(RefundBO refundBO) {

        List<Command> commands = refundGenerator.generatorCommands(refundBO);
        CollectionUtils.sort(commands);

        Map<String, CommandResult> results = commandExecutor.executeCommand(commands);

        refundBO.setRealFlag(true);
        processAfter(refundBO, results);
    }

    /**
     * 退款订单更新
     *
     * @param refundBO    退款订单信息
     */
    @Override
    protected void reStore(RefundBO refundBO) {

        log.debug("退款订单更新, parameter:{}",refundBO);
        if(PayStatusEnums.FAIL.getCode().equals(refundBO.getStatus())){

            transactionTemplate.execute(status -> {
                refundManager.lockAndValid(refundBO.getPayNo());

                payRefundAmtUpdate(refundBO);

                refundOrderRepository.update(refundBO);
                return null;
            });
        }else{
            refundOrderRepository.update(refundBO);
        }
    }

    /**
     * 退款失败时更新支付单可退款
     * @param refundBO
     */
    private void payRefundAmtUpdate(RefundBO refundBO) {
        //1、加载支付单
        //PayBO t = payOrderRepositoryImpl.load(refundBO.getOrigPayNo());

        //2 更新可退款余额
        modifyPayOrderRefundAmount(refundBO, refundBO.getOldPayBO());

    }

    /**
     * 原支付单退款金额更新
     *
     * @param payBO    原支付单信息
     */
    private void modifyPayOrderRefundAmount(RefundBO refundBO, PayBO payBO){

        log.debug("更新原支付单退款金额, refundBO:{}, payBO:{}", refundBO, payBO);

        //设置原支付主单退款金额
        payBO.setRefundAmt(payBO.getRefundAmt() - (refundBO.getAmt() - refundBO.getFeeAmt()));

        //设置原支付明细单退款金额
        setSubPayRefundAmt(payBO,refundBO);

        //原支付主单更新
        payManager.modifyRefundAmt(payBO);

        //原支付资金明细单更新
        payBO.getSubPayList().forEach(subPayManager::modifyRefundAmt);
    }

    /**
     * 更新支付子单的可退款金额
     * @param payBO
     * @param refundBO
     */
    private void setSubPayRefundAmt(PayBO payBO,RefundBO refundBO) {
        Long bankAmt = 0L;
        SubPayBO bankPayBO = null;
        for(SubPayBO subPayBO:payBO.getSubPayList()){
            if(subPayBO.getFundType().equals(FundTypeEnum.PAY.getCode())){
                bankPayBO = subPayBO;
                continue;
            }
            for(SubRefundBO subRefundBO:refundBO.getSubRefundBOs()){
                if(subPayBO.getFundDetailNo().equals(subRefundBO.getOrigRequestBizNo())){
                    log.debug("资金单号:{},资金类型:{},原退款金额:{},减去退款金额:{}",subPayBO.getFundDetailNo(),
                            subPayBO.getFundType(),subPayBO.getRefundAmount(),subRefundBO.getAmt());
                    subPayBO.setRefundAmount(subPayBO.getRefundAmount()-subRefundBO.getAmt());
                    bankAmt+=subRefundBO.getAmt();
                    break;
                }
            }
        }
        log.debug("银行单资金单号:{},银行单原退款金额:{},减去退款金额:{}",bankPayBO.getFundDetailNo(),
                bankPayBO.getRefundAmount(),bankAmt);
        bankPayBO.setRefundAmount(bankPayBO.getRefundAmount()-bankAmt);
    }

    /**
     * 指令完成后处理
     *
     * @param refundBO     退款BO对象
     * @param cmdResult    指令处理结果
     */
    @Override
    protected void processAfter(RefundBO refundBO, Map<String, CommandResult> cmdResult) {

        //遍历所有资金单返回结果
        for (Map.Entry<String, CommandResult> each : cmdResult.entrySet()) {
            setFundDetailStatus(each, refundBO);
        }
        finalCheck(refundBO, cmdResult);
    }

    /**
     * 设置资金单状态
     *
     * @param entry        处理结果
     * @param refundBO     退款订单
     */
    private void setFundDetailStatus(Map.Entry<String, CommandResult> entry, RefundBO refundBO) {

        SubRefundBO subRefundBO = refundBO.getFundDetail(refundBO.getSubRefundBOs(), entry.getKey());
        CommandResult commandResult = entry.getValue();
        subRefundBO.setTransReceiptNo(commandResult.getResponseNo());
        subRefundBO.setTransDate(commandResult.getResponseDate());
        subRefundBO.setErrorCode(commandResult.getResultCode());
        subRefundBO.setErrorMsg(commandResult.getResultMsg());
        subRefundBO.setStatus(commandResult.getStatus());
    }

    /**
     * 支付状态校验
     *
     * @param refundBO     退款订单
     * @param cmdResult    处理结果集
     */
    private void finalCheck(RefundBO refundBO, Map<String, CommandResult> cmdResult){
        log.debug("cmdResult:{}",cmdResult);
        for(CommandResult commandResult: cmdResult.values()){

            if(Objects.equals(PayStatusEnums.FAIL.getCode(), commandResult.getStatus())){
                this.refundFailProcess(refundBO,buildFailCallBack(commandResult.getResultCode(),commandResult.getResultMsg()));
                return;
            }
            if(Objects.equals(PayStatusEnums.INIT.getCode(), commandResult.getStatus())){
                refundBO.setErrorCode(commandResult.getResultCode());
                refundBO.setErrorMsg(commandResult.getResultMsg());
                refundBO.setStatus(PayStatusEnums.INIT.getCode());
                return;
            }
        }
        refundBO.setStatus(PayStatusEnums.SUCCESS.getCode());
    }

    /**
     * 回调
     *
     * @param callBackDTO    退款回调返回参数
     * @return               回调处理响应
     */
    @Override
    public RefundBO callBack(NotifyDTO callBackDTO) {

        log.debug("退款回调流程请求参数, callBackDTO:{}", callBackDTO);

        //获取回调对应退款资金单信息,并对回调参数进行校验
        RefundBO refundBO = refundManager.queryOrigOrderByPayNo(callBackDTO.getRequestOrderNo(),
                callBackDTO.getRequestBizNo(), callBackDTO.getAmt());

        //退款支付单
        PayBO oldPayBO = payOrderRepositoryImpl.load(refundBO.getOrigPayNo());
        refundBO.setOldPayBO(oldPayBO);

        //回调退款流程
        if(ClearStatusEnum.F.getCode().equals(callBackDTO.getStatus())){
            refundFailProcess(refundBO, callBackDTO);
        } else {
            refundSuccessProcess(refundBO, callBackDTO);
        }

        log.debug("退款回调流程状态推进, refundBO:{}", refundBO);

        reStore(refundBO);
        return refundBO;
    }

    /**
     * 清分回调 “失败状态” 处理流程
     *
     * @param refundBO       退款交易信息
     * @param callBackDTO    退款回调返回参数
     */
    private void refundFailProcess(RefundBO refundBO, NotifyDTO callBackDTO){

        List<SubRefundBO> subRefundBOs = refundBO.getSubRefundBOs();
        for(SubRefundBO subRefundBO : subRefundBOs){
            subRefundBO.setStatus(callBackDTO.getStatus());
            subRefundBO.setTransDate(callBackDTO.getResponseDate());

            if(StringUtils.equals(callBackDTO.getRequestOrderNo(), subRefundBO.getFundDetailNo())) {
                subRefundBO.setErrorCode(callBackDTO.getRespCode());
                subRefundBO.setErrorMsg(callBackDTO.getRespDesc());
            }
        }
        refundBO.setStatus(callBackDTO.getStatus());
        refundBO.setErrorCode(callBackDTO.getRespCode());
        refundBO.setErrorMsg(callBackDTO.getRespDesc());
    }


    /**
     * 清分回调 “成功状态” 处理流程
     *
     * @param refundBO       退款交易信息
     * @param callBackDTO    退款回调返回参数
     */
    private void refundSuccessProcess(RefundBO refundBO, NotifyDTO callBackDTO){

        // 退款回调校验
        boolean flag = refundStatusCheck(refundBO, callBackDTO);
        if(flag) {
            return;
        }

        // 状态推进处理流程
        refundStatusPush(refundBO, callBackDTO.getRequestBizNo());
    }

    /**
     * 退款回调校验
     * <p>
     * 退款回调校验，校验退款交易是否退款完成：
     * true:回调后退款完成，无需继续后续退款流程
     * false:回调后退款未完成，存在需要继续后续退款的资金单
     * </p>
     *
     * @param refundBO       退款订单单信息
     * @param callBackDTO    退款回调返回参数
     * @return               校验结果
     */
    private boolean refundStatusCheck(RefundBO refundBO, NotifyDTO callBackDTO){

        List<SubRefundBO> subRefundBOs = refundBO.getSubRefundBOs();

        SubRefundBO subRefundBO = refundBO.getFundDetail(subRefundBOs, callBackDTO.getRequestBizNo());

        subRefundBO.setStatus(callBackDTO.getStatus());
        subRefundBO.setTransDate(callBackDTO.getResponseDate());
        subRefundBO.setTransReceiptNo(callBackDTO.getClearNo());
        refundBO.setStatus(callBackDTO.getStatus());
        refundBO.setErrorCode(callBackDTO.getRespCode());
        refundBO.setErrorMsg(callBackDTO.getRespDesc());

        //判断是否存在未退款的资金单
        return subRefundBOs.size() <= 1;
    }

    /**
     * 状态推进处理流程
     *
     * @param refundBO        退款交易信息
     * @param fundDetailNo    资金明细单号
     */
    private void refundStatusPush(RefundBO refundBO, String fundDetailNo){

        //退款资金单按 “支付工具” 进行拆分
        Map<String,List<SubRefundBO>> map = refundSplitByPayToolType(refundBO.getSubRefundBOs());

        //退款指令
        List<Command> commands = new ArrayList<>();

        //获取退款策略
        List<RefundStrategyBO> list = refundStrategyManager.queryRefundProductStrategyMerge(
                refundBO.getOldPayBO().getBizType(), refundBO.getOldPayBO().getSubBizType());


        //根据退款策略, 对退款资金明细进行退款排序
        for(RefundStrategyBO refundStrategyBO : list){

            //退款策略 匹配 退款拆分后的资金单集合
            List<SubRefundBO> splitBOs = map.get(refundStrategyBO.getPayToolType());
            if(null == splitBOs) {
                continue;
            }

            for(SubRefundBO subRefundBO : splitBOs){

                //判断是否为当前回调的那笔 资金单,当前回调资金单无需继续退款
                if(StringUtils.equals(fundDetailNo, subRefundBO.getFundDetailNo())) {
                    continue;
                }

                //退款手续费指令生成
                if(FundTypeEnum.FEE.getCode().equals(subRefundBO.getFundType())){

                    commands.add(CommandConverter.balanceCommandConverter(subRefundBO, refundBO));
                    continue;
                }
                //退款指令生成
                if (PayToolRouteEnum.ACCOUNT_TRANSFER_ROUTE.containsPayTool(subRefundBO.getPayToolType())){

                    commands.add(CommandConverter.refundBalanceCommandConverter(subRefundBO, refundBO));

                } else if (PayToolRouteEnum.CLEAR_WEBPAY_ROUTE.containsPayTool(subRefundBO.getPayToolType())){

                    commands.add(CommandConverter.refundBankCommandConverter(subRefundBO, refundBO));
                }
            }
        }

        Map<String, CommandResult> results = commandExecutor.executeCommand(commands);
        log.debug("退款支付指令执行结果, result:{}", results);

        refundBO.setRealFlag(true);
        processAfter(refundBO, results);
    }

    /**
     * 退款资金单拆分
     * <p>
     * 退款交易根据支付工具进行拆分，如混合支付，原支付交易有余额、银行卡、红包等支付工具混合
     * 支付，则在退款时，针对原资金单可按支付工具进行拆分组合。
     * </p>
     * @param subRefundBOs    退款资金单信息
     * @return                退款资金单拆分后信息
     */
    private Map<String,List<SubRefundBO>> refundSplitByPayToolType(List<SubRefundBO> subRefundBOs){

        Map<String,List<SubRefundBO>> map = new HashMap<>();

        //退款资金单 按支付工具 进行拆分
        for(PayToolSubTypeEnum payToolSubTypeEnum : PayToolSubTypeEnum.values()) {

            List<SubRefundBO> splitBOs = new ArrayList<>();

            for(SubRefundBO subRefundBO : subRefundBOs){

                if(!StringUtils.equals(payToolSubTypeEnum.getSubPayToolCode(),subRefundBO.getClassType())){
                    continue;
                }
                splitBOs.add(subRefundBO);
                subRefundBO.setOperator(OperatorEnum.OPERATOR.getCode());
            }
            if(!CollectionUtils.isEmpty(splitBOs)) {
                map.put(payToolSubTypeEnum.getSubPayToolCode(),splitBOs);
            }
        }
        return map;
    }

    /**
     * 清分退款超时处理
     *
     * @param refundBO    退款主体
     */
    public ResponseBO clearRefundTimeOut(RefundBO refundBO,String fundDetailNo){

        ClearFundResBO clearFundResBO = clearQueryService.queryByRequestOrderNo(refundBO.getPayNo(),
                fundDetailNo);

        //退款支付单
        PayBO oldPayBO = payOrderRepositoryImpl.load(refundBO.getOrigPayNo());
        refundBO.setOldPayBO(oldPayBO);

        //没有查询到值,支付失败
        if (StringUtils.equals(PayCoreErrorCode.EMPTY_QUERY_RESULT.getErrorCode(), clearFundResBO.getErrorCode())) {
            this.refundFailProcess(refundBO,buildFailCallBack(PayCoreErrorCode.CLEAR_ORDER_NOT_EXIST.getErrorCode(),
                    PayCoreErrorCode.CLEAR_ORDER_NOT_EXIST.getErrorDesc()));
            reStore(refundBO);
            return new ResponseBO(PayStatusEnums.FAIL.getCode(), PayCoreErrorCode.CLEAR_ORDER_NOT_EXIST.getErrorCode(),
                    PayCoreErrorCode.CLEAR_ORDER_NOT_EXIST.getErrorDesc(), new Date(), null, fundDetailNo, true);
        }
        if(!clearFundResBO.isRealFlag()){
            return new ResponseBO(PayStatusEnums.INIT.getCode(), clearFundResBO.getErrorCode(),
                    clearFundResBO.getErrorMsg(), clearFundResBO.getResponseDate(), clearFundResBO.getResponseNo(),
                    fundDetailNo, false);
        }
        log.debug("退款回调流程状态推进, refundBO:{}", refundBO);
        switch (clearFundResBO.getPayStat()){
            case "F":
                this.refundFailProcess(refundBO,buildFailCallBack(clearFundResBO.getErrorCode(),clearFundResBO.getErrorMsg()));
                reStore(refundBO);
                return new ResponseBO(PayStatusEnums.FAIL.getCode(), clearFundResBO.getErrorCode(),
                        clearFundResBO.getErrorMsg(), new Date(), null, fundDetailNo, true);
            case "S":
                SubRefundBO bankRefundBO = refundBO.getBankFundDetail(refundBO.getSubRefundBOs());
                if(bankRefundBO!=null){
                    bankRefundBO.setStatus("S");
                }
                //状态推荐流程
                refundStatusPush(refundBO, fundDetailNo);
                //状态推进
                reStore(refundBO);
                return new ResponseBO(refundBO.getStatus(), clearFundResBO.getErrorCode(),
                        clearFundResBO.getErrorMsg(), clearFundResBO.getResponseDate(), clearFundResBO.getResponseNo(),
                        fundDetailNo, true);
            default:
                return new ResponseBO(PayStatusEnums.INIT.getCode(), clearFundResBO.getErrorCode(),
                        clearFundResBO.getErrorMsg(), clearFundResBO.getResponseDate(), clearFundResBO.getResponseNo(),
                        fundDetailNo, false);

        }
    }

    /**
     * 构建失败回调
     * @param code      错误码
     * @param desc      错误消息
     * @return
     */
    private NotifyDTO buildFailCallBack(String code, String desc) {
        NotifyDTO notifyDTO = new NotifyDTO();
        notifyDTO.setStatus(PayStatusEnums.FAIL.getCode());
        notifyDTO.setResponseDate(new Date());
        notifyDTO.setRespCode(code);
        notifyDTO.setRespDesc(desc);
        return notifyDTO;
    }


    /**
     * 需重试接口 对内部资金单的重试（订单状态推进）
     *
     * @param refundBO 退款订单对象
     * @return ResponseBO
     * @throws RuntimeException 出现异常时表示重试没有成功（子类方法实现中不可捕获异常）
     */
    public ResponseBO dalInnerFundProc(RefundBO refundBO) throws RuntimeException {
        SubRefundBO subRefundBO = refundBO.getBankFundDetail(refundBO.getSubRefundBOs());
        //先判断银行单是否成功,成功处理后面流程,不成功不处理
        if (!PayStatusEnums.SUCCESS.getCode().equals(subRefundBO.getStatus())){
            log.error("退款重试 银行单未成功 支付单号:{} 银行单号:{} 银行单状态:{}", refundBO.getPayNo(),
                    subRefundBO.getFundDetailNo(), subRefundBO.getStatus());
            return null;
        }
        refundBO.setSubRefundBOs(refundBO.getSubRefundBOs().stream().
                filter(a->a.getStatus().equals(PayStatusEnums.INIT.getCode())).collect(Collectors.toList()));
        //状态推荐流程
        refundStatusPush(refundBO, subRefundBO.getFundDetailNo());
        //状态推进
        reStore(refundBO);
        return new ResponseBO(refundBO.getStatus(), subRefundBO.getErrorCode(),
                subRefundBO.getErrorMsg(), new Date(), null,
                subRefundBO.getFundDetailNo(), true);
    }
}
